﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using AWS.SLAM.SharePoint;

namespace AWS.SLAM.Configuration
{
    /// <summary>
    /// The XmlConfigurationManager provides access to an XML file represetnation of a SLAM configuration.
    /// XmlConfigurationManager is the default implementation of IConfigurationManager.  Its DataMapping
    /// is an XmlSqlDataMapping and it also registers an XmlConnectionStringsSection as a default source
    /// of connection strings.
    /// </summary>
    /// <remarks>
    /// The XmlConfigurationManager class loads a SLAM configuration from an XML file that follows an expected schema.
    /// The SLAM configuration is bounded by <![CDATA[<Configuration>]]> tags.  The DataMapping is bounded by <![CDATA[<DataMapping>]]> tags and consists of nested <![CDATA[<List>]]> and <![CDATA[<ContentType>]]> tags.
    /// These <![CDATA[<List>]]> and <![CDATA[<ContentType>]]> tags are considered "configured types".  Configured types consist of a Field collection.  The following is an example
    /// XML SLAM configuration with basic DataMapping.
    /// </remarks>
    public class XmlConfigurationManager : IConfigurationManager
    {
        #region Private State

        /// <summary>
        /// Configuration file XDocument
        /// </summary>
        private XDocument _configXml;

        /// <summary>
        /// Data Mappings
        /// </summary>
        private Dictionary<string, DataMapping> _dataMappings;

        /// <summary>
        /// Dictionary of registered section delegates.
        /// </summary>
        private Dictionary<string, Func<object, object>> _sectionDelegates;

        #endregion

        #region Properties

        /// <summary>
        /// Gets XML configuration's DataMapping section based on the current SLAM Item Context.
        /// </summary>
        public DataMapping DataMapping
        {
            get
            {   
                DataMapping currentDataMapping = _dataMappings["DEFAULTMAPPING"];
                if (SlamItemContext.ListItem != null)
                {
                    string siteCollection = SlamItemContext.ListItem.Web.Site.ServerRelativeUrl.TrimStart('/');
                    if (!String.IsNullOrEmpty(siteCollection) && _dataMappings.ContainsKey(siteCollection))
                    {
                        currentDataMapping = _dataMappings[siteCollection];
                    }
                }
                return currentDataMapping;
            }
        }

        /// <summary>
        /// All of XML configuration's DataMapping sections.
        /// </summary>
        public Dictionary<string, DataMapping> DataMappings
        {
            get
            {
                return _dataMappings;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new XmlConfigurationManager instance, where the loaded configuration file is assumed
        /// to be "slam.xml" in the BaseDirectory of the current AppDomain.
        /// 
        /// The "ConnectionStrings" configuration section is then registered as a XmlConnectionStringSection.
        /// </summary>
        public XmlConfigurationManager()
        {
            string config = String.Format("{0}\\{1}", AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\'), "slam.config");            
            _configXml = XDocument.Load(config);

            LoadDataMappings();

            _sectionDelegates = new Dictionary<string, Func<object, object>>();

            RegisterSection("ConnectionStrings",
                configInput =>
                {
                    object section = null;
                    if (configInput is XDocument)
                    {
                        section = new XmlConnectionStringSection((XDocument)configInput);
                    }
                    return section;
                });
        }

        /// <summary>
        /// Creates a new XmlConfigurationManager instance.  Sets the configuration XML file location to the
        /// given configurationLocation.  If configurationLocation is null or empty, the configuration file is assumed
        /// to be "slam.xml" in the BaseDirectory of the current AppDomain.
        /// 
        /// The "ConnectionStrings" configuration section is then registered as a XmlConnectionStringSection.
        /// </summary>
        /// <param name="configurationLocation"></param>
        public XmlConfigurationManager(string configurationLocation)
        {
            string config = "";
            if (String.IsNullOrEmpty(configurationLocation))
            {
                config = String.Format("{0}\\{1}", AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\'), "slam.config");
            }
            else
            {
                config = configurationLocation;
            }

            _configXml = XDocument.Load(config);
            LoadDataMappings();

            _sectionDelegates = new Dictionary<string, Func<object, object>>();

            RegisterSection("ConnectionStrings",
                configInput =>
                {
                    object section = null;
                    if (configInput is XDocument)
                    {
                        section = new XmlConnectionStringSection((XDocument)configInput);
                    }
                    return section;
                });
        }

        #endregion

        

        #region IConfigurationManager Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sectionName"></param>
        /// <returns></returns>
        public object GetSection(string sectionName)
        {
            object section = null;
            if (_sectionDelegates.ContainsKey(sectionName))
            {
                section = _sectionDelegates[sectionName].Invoke(_configXml);
            }
            else
            {
                throw new ConfigurationSectionNotFoundException(String.Format("SLAM Configuration \"{0}\" Section cannot be found.", sectionName));
            }
            
            return section;
        }

        /// <summary>
        /// Returns a registered typed custom configuration section by name.
        /// </summary>
        /// <typeparam name="SECTIONTYPE">Type of section expected</typeparam>
        /// <param name="sectionName">Name of the section as registered</param>
        /// <returns>Strongly typed object representing a section</returns>
        public SECTIONTYPE GetSection<SECTIONTYPE>(string sectionName)
        {
            object section = null;
            if (_sectionDelegates.ContainsKey(sectionName))
            {
                section = _sectionDelegates[sectionName].Invoke(_configXml);
            }
            else
            {
                throw new ConfigurationSectionNotFoundException(String.Format("SLAM Configuration \"{0}\" Section cannot be found.", sectionName));
            }
            
            return (SECTIONTYPE)section;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sectionName"></param>
        /// <param name="getSection"></param>
        public void RegisterSection(string sectionName, Func<object, object> getSection)
        {
            if (!_sectionDelegates.ContainsKey(sectionName))
            {
                _sectionDelegates.Add(sectionName, getSection);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sectionName"></param>
        /// <param name="getSection"></param>
        /// <param name="replace"></param>
        public void RegisterSection(string sectionName, Func<object, object> getSection, bool replace)
        {
            if (!_sectionDelegates.ContainsKey(sectionName))
            {
                _sectionDelegates.Add(sectionName, getSection);
            }
            else if (replace)
            {
                _sectionDelegates[sectionName] = getSection;
            }
        }

        #endregion

        #region Private Members

        public void LoadDataMappings()
        {
            if (_dataMappings == null)
            {
                _dataMappings = new Dictionary<string, DataMapping>();
            }

            if (_configXml.Root.Elements().Where(node => node.Name == "DataMapping").Count() > 1)
            {
                bool containsRoot = _configXml.Root.Elements().Where(
                                        node => node.Name == "DataMapping" && 
                                                (node.Attribute("SiteCollection") == null || 
                                                String.IsNullOrEmpty(node.Attribute("SiteCollection").Value))).Count() > 0;

                foreach (XElement node in _configXml.Root.Elements().Where(node => node.Name == "DataMapping"))
                {
                    if (containsRoot && (node.Attribute("SiteCollection") == null || String.IsNullOrEmpty(node.Attribute("SiteCollection").Value)) && !_dataMappings.ContainsKey("DEFAULTMAPPING"))
                    {
                        //Create default mapping called DEFAULTMAPPING.  There is a good chance no other site collection will have this name.
                        _dataMappings.Add("DEFAULTMAPPING", new XmlSqlDataMapping(_configXml));
                    }
                    else if (!containsRoot && !_dataMappings.ContainsKey("DEFAULTMAPPING"))
                    {
                        //Create default mapping called DEFAULTMAPPING.  There is a good chance no other site collection will have this name.
                        _dataMappings.Add("DEFAULTMAPPING", new XmlSqlDataMapping(_configXml));
                    }
                    else if(node.Attribute("SiteCollection") != null)
                    {
                        _dataMappings.Add(node.Attribute("SiteCollection").Value, new XmlSqlDataMapping(_configXml, node.Attribute("SiteCollection").Value));
                    }
                }
            }
            else if (_configXml.Root.Elements().Where(node => node.Name == "DataMapping").Count() > 0)
            {
                //Create default mapping called DEFAULTMAPPING.  There is a good chance no other site collection will have this name.
                _dataMappings.Add("DEFAULTMAPPING", new XmlSqlDataMapping(_configXml));
            }
            else
            {
                throw new ConfigurationErrorsException("SLAM XML Configuration does not contain any DataMapping sections.  The SLAM XML Configuraiton must contain at least one DataMapping section.");
            }
        }

        #endregion
    }
}
